/*-------------------------------------------------------------------------
 *
 * win32setlocale.c
 *		用于解决Windows setlocale()实现中的错误的包装器
 *
 * Copyright (c) 2011-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  src/port/win32setlocale.c
 *
 *
 * Windows中的setlocale()函数存在两个问题。首先，它对国家名称中带有点的区域名称存在问题。例如：
 *
 * "Chinese (Traditional)_Hong Kong S.A.R..950"
 *
 * 出于某种原因，setlocale()不接受该参数，即使setlocale(LC_ALL, NULL)确实返回该内容。
 * 幸运的是，它接受各种此类国家的替代名称，因此为了解决损坏的setlocale()函数，我们在调用setlocale()
 * 之前将问题区域名称映射到被接受的别名。
 *
 * 第二个问题是"Norwegian (Bokmål)"的区域名称包含一个非ASCII字符。这是一个问题，因为当你
 * 尚未设置区域时，尚不清楚区域名称应采用什么编码。此外，当集群包含具有不同编码的数据库时，会导致问题
 * ，因为区域名称存储在pg_database系统目录中。为了解决这个问题，当setlocale()
 * 返回该区域名称时，将其映射到同一区域的纯ASCII别名。
 *-------------------------------------------------------------------------
 */

#include "c.h"

#undef setlocale

struct locale_map
{
	/*
	 * 要替换的区域设置名称中的字符串。可以是单个字符串（以NULL结束），
	 * 或者是单独的起始和结束字符串。如果给出了两个字符串，则区域设置
	 * 名称必须同时包含这两个字符串，并且它们之间的所有内容都被替换。
	 * 这用于简单的正则表达式搜索，允许替换“start.*end”。
	 */
	const char *locale_name_start;
	const char *locale_name_end;

	const char *replacement;	/* 用于替换匹配项的字符串 */
};

/*
 * 在调用setlocale()之前应用于参数的映射。
 */
static const struct locale_map locale_map_argument[] = {
	/*
	 * “HKG”在这里列出：
	 * http://msdn.microsoft.com/en-us/library/cdax410z%28v=vs.71%29.aspx
	 * （国家/地区字符串）。
	 *
	 * “ARE”是阿联酋的ISO-3166三字母代码。它不在
	 * 上面的列表中，但似乎仍然可以工作。
	 */
	{"Hong Kong S.A.R.", NULL, "HKG"},
	{"U.A.E.", NULL, "ARE"},

	/*
	 * 澳门特别行政区的ISO-3166国家代码是MAC，但Windows似乎
	 * 并不识别这个。澳门也没有在上述链接的接受缩写表中列出。
	 * 幸运的是，“ZHM”似乎被接受为“中文（繁体）_澳门特别行政区.950”的
	 * 别名。我不确定“ZHM”来自哪里，必须是某种遗留命名方案。但好吧，
	 * 它确实有效。
	 *
	 * 请注意，与HKG和ARE不同，ZHM是*整个*区域设置名称的别名，
	 * 而不仅仅是国家部分。
	 *
	 * 一些版本的Windows将其拼写为“Macau”，其他版本则为“Macao”。
	 */
	{"Chinese (Traditional)_Macau S.A.R..950", NULL, "ZHM"},
	{"Chinese_Macau S.A.R..950", NULL, "ZHM"},
	{"Chinese (Traditional)_Macao S.A.R..950", NULL, "ZHM"},
	{"Chinese_Macao S.A.R..950", NULL, "ZHM"},
	{NULL, NULL, NULL}
};

/*
 * 在调用setlocale()之后应用于其返回值的映射。
 */
static const struct locale_map locale_map_result[] = {
	/*
	 * “挪威语（Bokm&aring;l）”区域设置名称包含a-ring字符。
	 * 将其映射到纯ASCII别名。
	 *
	 * 不清楚setlocale()在返回区域设置名称时使用什么编码，
	 * 所以为了安全起见，我们搜索“挪威语（Bok*l）”。
	 *
	 * 更复杂的是，某些版本的Windows将区域设置名称拼写为没有
	 * 括号的形式。也要翻译这个。
	 */
	{"Norwegian (Bokm", "l)_Norway", "Norwegian_Norway"},
	{"Norwegian Bokm", "l_Norway", "Norwegian_Norway"},
	{NULL, NULL, NULL}
};

#define MAX_LOCALE_NAME_LEN		100

static const char * fc_map_locale(const struct locale_map *fc_map, const char *fc_locale)
{
	static char fc_aliasbuf[MAX_LOCALE_NAME_LEN];
	int			fc_i;

	/* 检查区域设置名称是否与任何有问题的名称匹配。 */
	for (fc_i = 0; fc_map[fc_i].locale_name_start != NULL; fc_i++)
	{
		const char *fc_needle_start = fc_map[fc_i].locale_name_start;
		const char *fc_needle_end = fc_map[fc_i].locale_name_end;
		const char *fc_replacement = fc_map[fc_i].replacement;
		char	   *fc_match;
		char	   *fc_match_start = NULL;
		char	   *fc_match_end = NULL;

		fc_match = strstr(fc_locale, fc_needle_start);
		if (fc_match)
		{
			/*
			 * 找到了第一个部分的匹配。如果这是一个两部分的
			 * 替换，找到第二部分。
			 */
			fc_match_start = fc_match;
			if (fc_needle_end)
			{
				fc_match = strstr(fc_match_start + strlen(fc_needle_start), fc_needle_end);
				if (fc_match)
					fc_match_end = fc_match + strlen(fc_needle_end);
				else
					fc_match_start = NULL;
			}
			else
				fc_match_end = fc_match_start + strlen(fc_needle_start);
		}

		if (fc_match_start)
		{
			/* 找到匹配项。替换匹配的字符串。 */
			int			fc_matchpos = fc_match_start - fc_locale;
			int			fc_replacementlen = strlen(fc_replacement);
			char	   *fc_rest = fc_match_end;
			int			fc_restlen = strlen(fc_rest);

			/* 检查结果是否适合静态缓冲区 */
			if (fc_matchpos + fc_replacementlen + fc_restlen + 1 > MAX_LOCALE_NAME_LEN)
				return NULL;

			memcpy(&fc_aliasbuf[0], &fc_locale[0], fc_matchpos);
			memcpy(&fc_aliasbuf[fc_matchpos], fc_replacement, fc_replacementlen);
			/* 包括空终止符 */
			memcpy(&fc_aliasbuf[fc_matchpos + fc_replacementlen], fc_rest, fc_restlen + 1);

			return fc_aliasbuf;
		}
	}

	/* 没有匹配项，返回原始字符串 */
	return fc_locale;
}

char * pgwin32_setlocale(int fc_category, const char *fc_locale)
{
	const char *fc_argument;
	char	   *fc_result;

	if (fc_locale == NULL)
		fc_argument = NULL;
	else
		fc_argument = fc_map_locale(locale_map_argument, fc_locale);

	/* 调用真实的setlocale()函数 */
	fc_result = setlocale(fc_category, fc_argument);

	/*
	 * setlocale()被规定返回一个“char *”，调用者
	 * 禁止修改，因此消除“const”是无害的。
	 */
	if (fc_result)
		fc_result = unconstify(char *, fc_map_locale(locale_map_result, fc_result));

	return fc_result;
}
